package com.basker.pisces.core.query.meta.bean;

import java.util.HashMap;
import java.util.Map;
import java.util.Stack;

import com.basker.pisces.core.enums.TableType;
import com.basker.pisces.core.meta.ICollectionField;
import com.basker.pisces.core.meta.IDataField;
import com.basker.pisces.core.meta.IDataObjectMeta;
import com.basker.pisces.core.meta.IReferenceField;
import com.basker.pisces.core.meta.annotation.DataObjectMeta;
import com.basker.pisces.core.meta.bean.BeanMetaManager;
import com.basker.pisces.core.query.meta.FieldDesc;
import com.basker.pisces.core.query.meta.PkFieldDesc;
import com.basker.pisces.core.query.meta.QueryMeta;
import com.basker.pisces.core.query.meta.ReferenceFieldDesc;
import com.basker.pisces.core.query.meta.TableDesc;
import com.basker.pisces.exception.ExceptionFactory;
import com.basker.pisces.utils.ClassUtils;

public class BeanQueryMeta extends QueryMeta {

    private static Map<String, BeanQueryMeta> BEAN_QUERY_METAS = new HashMap<>();

    /**
     * 获取
     *
     * @param beanClass
     * @return
     */
    public static BeanQueryMeta get(Class<?> beanClass) {
        if (!beanClass.isAnnotationPresent(DataObjectMeta.class)) {
            throw new IllegalArgumentException("parameter 'beanClass' has no 'DataObjectMeta' annotation ");
        }

        String name = beanClass.getName();
        BeanQueryMeta queryMeta = BEAN_QUERY_METAS.get(name);

        if (queryMeta == null) {
            synchronized (BEAN_QUERY_METAS) {
                queryMeta = BEAN_QUERY_METAS.get(name);

                if (queryMeta == null) {
                    queryMeta = new BeanQueryMeta(beanClass);
                    BEAN_QUERY_METAS.put(name, queryMeta);
                }
            }
        }

        return queryMeta;
    }

    private transient Class<?> beanClass;
    private String beanClassName;

    public BeanQueryMeta(Class<?> beanClass) {
        super(beanClass.getSimpleName());
        this.beanClass = beanClass;
        this.beanClassName = this.beanClass.getName();

        IDataObjectMeta meta = BeanMetaManager.getBeanMeta(beanClass);
        this.setDisplayName(meta.getDisplayName());
        this.setSchema(meta.getSchema());

        this.addTable(null, meta, new TableContext());
    }

    public Class<?> getBeanClass() {
        if (this.beanClass == null) {
            this.beanClass = ClassUtils.getClass(this.beanClassName);
        }

        return this.beanClass;
    }

    private void addFieldToTable(FieldDesc fieldDesc) {
        TableDesc hostTable = fieldDesc.getHostTable();
        if (hostTable != null) {
            hostTable.getFields().add(fieldDesc);
        } else {
            throw ExceptionFactory.createRuntimeException("BeanQueryMeta.addFieldToTable-001",
                    "fieldDesc:{0}'s hostTable is null!", fieldDesc.getId());
        }
    }

    private void addRefFields(ReferenceFieldDesc refFieldDesc, IReferenceField refField, TableContext context) {
        IDataObjectMeta refMeta = refField.getDataObjectMeta();

        // 5.创建表字段信息
        for (IDataField dataField : refMeta.getDataFields()) {
            // 主键不需要添加
            if (dataField.isPkField()) {
                continue;
            }

            // 创建字段
            FieldDesc fieldDesc = this.createSubFieldDesc(refFieldDesc, dataField);

            // 添加到表中
            this.addFieldToTable(fieldDesc);
        }

        if (context.refFieldDepth() < 2) {
            // 6.创建关联字段(refmember)的信息
            for (IReferenceField subRefField : refMeta.getReferenceFields()) {
                // 创建字段

                ReferenceFieldDesc subRefFieldDesc = this.createSubRefFieldDesc(refFieldDesc, subRefField);

                // 添加到表中
                this.addFieldToTable(subRefFieldDesc);

                context.addRefField(subRefFieldDesc);
                try {
                    this.addRefFields(subRefFieldDesc, subRefField, context);
                } finally {
                    context.removeRefField();
                }
            }

            // 7.创建子表的信息
//			for (ICollectionField subColField : refMeta.getCollectionFields()) {
//				IDataObjectMeta childMeta = subColField.getItemObjectMeta();
//
//				TableDesc childRefTable = this.getChildRefTable(refFieldDesc.getId(), childMeta,
//						refFieldDesc.getRefTable());
//
//				// 5.创建表字段信息
//				for (IDataField dataField : childMeta.getDataFields()) {
//					// 主键不需要添加
//					if (dataField.isPkField()) {
//						continue;
//					}
//
//					// 创建字段
//					String fieldId = refFieldDesc.getId() + "." + subColField.getName() + "." + dataField.getName();
//					String name = dataField.getDbFieldName();
//					String alias = fieldId;
//					String displayName = dataField.getDisplayName();
//
//					FieldDesc childFieldDesc = new FieldDesc(fieldId, name, alias, displayName,
//							dataField.getFieldClass());
//
//					// 设置所属表
//					childFieldDesc.setHostTable(childRefTable);
//
//					// 添加到表中
//					this.addFieldToTable(childFieldDesc);
//				}
//			}
        }

        // 构建引用表上的字段的映射，后续会用到
        refFieldDesc.buildReferencedFields();
    }

    // 添加javabean对应的数据库表字段信息
    private void addTable(IDataObjectMeta parentMeta, IDataObjectMeta currentMeta, TableContext context) {
        // 1.创建currentMeta对应的表
        String tableName = currentMeta.getTableName();
        IDataField pkField = currentMeta.getPkField();

        String pkFieldId = context.resolveWithPrefix(pkField.getName());
        String pkFieldName = pkField.getDbFieldName();

        TableDesc currentTableDesc = createTableDesc(tableName, pkFieldId, pkFieldName);

        this.getTables().add(currentTableDesc);

        if (pkField != null) {
            // 2.添加主键字段到字段集合
            PkFieldDesc pkFieldDesc = currentTableDesc.getPkField();
            pkFieldDesc.setValueType(pkField.getFieldClass());

            currentTableDesc.getFields().add(pkFieldDesc);
        }

        // 3.设置表信息
        if (parentMeta == null) {// 主表
            currentTableDesc.setType(TableType.Main);

            this.setMainTable(currentTableDesc);
        } else {
            // 当前表为子表
            currentTableDesc.setType(TableType.Child);

            TableDesc parentTable = context.getParentTable();
            currentTableDesc.setFkFieldName(parentTable.getPkFieldName());
            currentTableDesc.setParentTable(parentTable);
        }

        // 4.创建 一对一扩展表
        String[] subTables = currentMeta.getSubTableNames();
        if (subTables != null) {
            for (String subTable : subTables) {
                TableDesc subTableDesc = this.createTableDesc(subTable, pkField);

                subTableDesc.setType(TableType.Sub);
                subTableDesc.setFkFieldName(pkFieldName);
                subTableDesc.setParentTable(currentTableDesc);

                this.getTables().add(subTableDesc);
            }
        }

        // 5.创建表字段信息
        for (IDataField dataField : currentMeta.getDataFields()) {
            // 主键已经添加
            if (dataField.isPkField()) {
                continue;
            }

            // 外键对象的id字段也不用添加，它由外键字段对应RefFieldDesc处理
            if (this.isRefIdFiled(currentMeta, dataField)) {
                continue;
            }

            // 创建字段
            FieldDesc fieldDesc = this.createFieldDesc(context, currentMeta, dataField);

            // 添加到表中
            this.addFieldToTable(fieldDesc);
        }

        // 6.创建关联字段(refmember)的信息
        for (IReferenceField refField : currentMeta.getReferenceFields()) {
            // 创建字段
            ReferenceFieldDesc refFieldDesc = this.createRefFieldDesc(context, currentMeta, refField);

            // 添加到表中
            this.addFieldToTable(refFieldDesc);

            context.addRefField(refFieldDesc);
            try {
                this.addRefFields(refFieldDesc, refField, context);
            } finally {
                context.removeRefField();
            }
        }

        // 7.子Javabean表字段信息
        for (ICollectionField collectionField : currentMeta.getCollectionFields()) {
            context.addPrefix(collectionField.getName());
            context.addTable(currentTableDesc);

            try {
                this.addTable(currentMeta, collectionField.getItemObjectMeta(), context);
            } finally {
                context.removePrefix();
                context.removeTable();
            }
        }
    }

    private FieldDesc createFieldDesc(TableContext context, IDataObjectMeta currentMeta, IDataField dataField) {
        String fieldId = context.resolveWithPrefix(dataField.getName());
        String name = dataField.getDbFieldName();
        String alias = fieldId;
        String displayName = dataField.getDisplayName();
        Class<?> fieldClass = dataField.getFieldClass();

        FieldDesc fieldDesc = new FieldDesc(fieldId, name, alias, displayName, fieldClass);

        // 设置所属表
        TableDesc hostTable = this.findHostTable(currentMeta, dataField);
        fieldDesc.setHostTable(hostTable);

        return fieldDesc;
    }

    private ReferenceFieldDesc createRefFieldDesc(TableContext context, IDataObjectMeta currentMeta,
                                                  IReferenceField refField) {

        String fieldId = context.resolveWithPrefix(refField.getName());
        IDataField refIdField = refField.getReferenceIdField();
        String name = refIdField.getDbFieldName();
        String alias = fieldId;
        String displayName = refField.getDisplayName();

        IDataObjectMeta refMeta = refField.getDataObjectMeta();

        ReferenceFieldDesc refFieldDesc = new ReferenceFieldDesc(fieldId, name, alias, displayName, refMeta);

        // 设置所属表
        TableDesc hostTable = this.findHostTable(currentMeta, refIdField);
        refFieldDesc.setHostTable(hostTable);

        // 设置关联表
        TableDesc refTable = this.getRefTable(fieldId, refField, refMeta, hostTable);
        refFieldDesc.setReferenceTable(refTable);

        return refFieldDesc;

    }

    /**
     * 创建refTable表上的字段
     *
     * @param preField
     * @param dataField
     * @return
     */
    private FieldDesc createSubFieldDesc(ReferenceFieldDesc preField, IDataField dataField) {
        String fieldId = preField.getId() + "." + dataField.getName();
        String name = dataField.getDbFieldName();
        String alias = fieldId;
        String displayName = preField.getDisplayName() + dataField.getDisplayName();
        Class<?> fieldClass = dataField.getFieldClass();

        FieldDesc fieldDesc = new FieldDesc(fieldId, name, alias, displayName, fieldClass);

        // 属性字段的所属表即上一级引用字段的关联表
        fieldDesc.setHostTable(preField.getReferenceTable());

        // 将fieldDesc关联到preField上
        if (dataField.equals(preField.getReferenceObjectMeta().getCodeField())) {
            preField.setReferencedCodeField(fieldDesc);
        } else if (dataField.equals(preField.getReferenceObjectMeta().getNameField())) {
            preField.setReferencedNameField(fieldDesc);
        } else {
            preField.addOtherReferencedField(fieldDesc);
        }

        return fieldDesc;
    }

    /**
     * 设置reftable表上的ref字段
     *
     * @param preField
     * @param refField
     * @return
     */
    private ReferenceFieldDesc createSubRefFieldDesc(ReferenceFieldDesc preField, IReferenceField refField) {
        String fieldId = preField.getId() + "." + refField.getName();
        String displayName = preField.getDisplayName() + refField.getDisplayName();

        IDataField refIdField = refField.getReferenceIdField();
        String name = refIdField.getDbFieldName();
        String alias = fieldId;

        IDataObjectMeta refMeta = refField.getDataObjectMeta();

        ReferenceFieldDesc refFieldDesc = new ReferenceFieldDesc(fieldId, name, alias, displayName, refMeta);

        // 属性字段的所属表即上一级引用字段的关联表
        refFieldDesc.setHostTable(preField.getReferenceTable());

        // 设置关联表
        TableDesc refTable = this.getRefTable(fieldId, refField, refMeta, preField.getReferenceTable());
        refFieldDesc.setReferenceTable(refTable);

        // 将fieldDesc关联到preField上
        preField.addOtherReferencedField(refFieldDesc);

        return refFieldDesc;
    }

    private TableDesc findHostTable(IDataObjectMeta currentMeta, IDataField dataField) {
        String tableName = dataField.getHostTableName() != null ? dataField.getHostTableName()
                : currentMeta.getTableName();
        return this.getTable(tableName);
    }

//	/**
//	 * refTable的childTable
//	 * 
//	 * @param fieldId
//	 * @param refMeta
//	 * @param parentTable
//	 * @return
//	 */
//	private TableDesc getChildRefTable(String fieldId, IDataObjectMeta refMeta, TableDesc parentTable) {
//		IDataField pkField = refMeta.getPkField();
//		TableDesc refTable = this.getRefTableDesc(fieldId, refMeta.getTableName(), pkField.getName(),
//				pkField.getDbFieldName());
//
//		refTable.setType(TableType.Child);
//		refTable.setFkFieldName(parentTable.getPkFieldName());
//		refTable.setParentTable(parentTable);
//
//		return refTable;
//	}

    private TableDesc getRefTable(String fieldId, IReferenceField refField, IDataObjectMeta refMeta,
                                  TableDesc parentTable) {
        IDataField pkField = refMeta.getPkField();
        TableDesc refTable = this.getRefTableDesc(fieldId, refMeta.getTableName(), pkField.getName(),
                pkField.getDbFieldName());

        refTable.setFkFieldName(refField.getReferenceIdField().getDbFieldName());
        refTable.setParentTable(parentTable);

        return refTable;
    }

    private boolean isRefIdFiled(IDataObjectMeta meta, IDataField dataField) {
        String name = dataField.getName();
        if (name.endsWith(IDataField.ID_FIELD_NAME)) {
            String resolvedName = name.substring(0, name.length() - 2);
            return meta.getField(resolvedName) instanceof IReferenceField;
        }

        return false;
    }

    class TableContext {
        private Stack<TableDesc> tables;
        private Stack<String> prefixes;
        private Stack<ReferenceFieldDesc> refFields;

        public int refFieldDepth() {
            if (refFields == null) {
                return 0;
            }

            return this.refFields.size();
        }

        void addPrefix(String prefix) {
            if (prefixes == null) {
                prefixes = new Stack<String>();
            }

            prefixes.push(prefix);
        }

        void addRefField(ReferenceFieldDesc refField) {
            if (refFields == null) {
                refFields = new Stack<>();
            }

            refFields.push(refField);
        }

        void addTable(TableDesc table) {
            if (tables == null) {
                tables = new Stack<TableDesc>();
            }

            tables.push(table);
        }

        TableDesc getParentTable() {
            if (tables != null) {
                return tables.peek();
            }

            return null;
        }

        void removePrefix() {
            if (prefixes != null) {
                prefixes.pop();
            }
        }

        void removeRefField() {
            if (refFields != null) {
                refFields.pop();
            }
        }

        void removeTable() {
            if (tables != null) {
                tables.pop();
            }
        }

        String resolveWithPrefix(String value) {
            if (this.prefixes == null) {
                return value;
            }

            StringBuilder prefix = new StringBuilder();
            for (String p : prefixes) {
                prefix.append(p);
                prefix.append(".");
            }

            prefix.append(value);

            return prefix.toString();

        }

    }

}
